var FMFPdxfConverterSVGImport = Class.create();
FMFPdxfConverterSVGImport.prototype = {
    initialize: function () {
        this.utils = new FMFPAppUtils();
        this.entityToPolyline = new FMFPentityToPolyline();
        this.layerUtils = new x_lsmcb_fm.FMFPLayerUtils();
        this.layerUtilsImport = new x_lsmcb_fm.FMFPLayerUtilsImport();
        this.dxfMapper = new FMFPdxfMapper();
    },

    //TODO: circle, polyline, bezier

    type: 'FMFPdxfConverterSVGImport',

    //does all the operations and uses all the layers from the DXF
    convertAndSave: function (versionSysId, attachmentSysId, isNewFloor, manualConversionLayers) {
        var convertionSource = 'manual';//for now still not saving the jpeg images
        if(manualConversionLayers && manualConversionLayers.length > 0) convertionSource = 'manual_dxf';
        else convertionSource = 'autocad';
        
        var dxfString = this.load(attachmentSysId);
        if (!dxfString.success)
            return dxfString;
        dxfString = dxfString.result;

        var parsedDXF = new FMFPdxfParser().parseSync(dxfString);

        var configuredLayers = this.layerUtilsImport.getConfiguredLayers(versionSysId, convertionSource, manualConversionLayers).result;

        var entities = this.denormalize(parsedDXF, configuredLayers, versionSysId, isNewFloor, convertionSource);
        var denormalized = entities.denormalized;

        var convertedDXF = this.convert(entities.entitiesByLayer, parsedDXF, entities.evaluatedLSC);

        this.layerUtilsImport.saveLayer(versionSysId, convertedDXF, entities.evaluatedLSC);
        //return validation;
        return true;
    },

    //Loads the DXF string and set the property DXF_String
    load: function (attachmentSysId) {

        //sets the DXF_String property
        var result = '',
            ln = '',
            reader = new GlideTextReader(new GlideSysAttachment().getContentStream(attachmentSysId));

        while ((ln = reader.readLine()) != null)
            result += ln + '\n';

        if (result)
            return new FMFPOperationResult(true, "Attachment loaded", result);

        return new FMFPOperationResult(false, "Attachment failed to load");
    },

    convert: function (entitiesByLayer, parsed, evaluatedLSC) {

        var objectContext = this,
            boundsAndElement = null;
        objectContext.bgGeoJSON = {
            type: 'FeatureCollection',
            features: []
        };

        this.facilityFlag = true;

        var entitiesArray = [];

        Object.keys(entitiesByLayer).forEach(function (layerEntities) {
            entitiesByLayer[layerEntities].forEach(function (entity) {
                entitiesArray.push(entity);
            });
        });
        
        var _entitiesReduce = entitiesArray.reduce(function (acc, entity, i) {
            
            //if (entity.handle === 'D8EC2') {
            //if (entity.layer === 'FN' || entity.layer === 'GROS$' || entity.layer === 'PL-TOIL') {
            //if (entity.type === 'LINE' || entity.type === 'LWPOLYLINE' || entity.type === 'POLYLINE' || entity.type === 'CIRCLE') {
            if (entity.type === 'LINE' || entity.type === 'LWPOLYLINE' || entity.type === 'POLYLINE' || entity.type === 'ELLIPSE' || entity.type === 'ARC') {
                var rgb = objectContext.getRGBForEntity(parsed.tables.layer, entity);

                entity.transformMatrix = objectContext.getTransformMatrix(entity.transforms);

                // Ignore entities like MTEXT that don't produce SVG elements
                boundsAndElement = objectContext.entityToBoundsAndElement(entity);
                if (boundsAndElement) {

                    var element = boundsAndElement.element;
                    var bbox = boundsAndElement.bbox;

                    if(evaluatedLSC.geojson) {
                        evaluatedLSC.geojson.forEach(function (layerConfig) {
                            layerConfig.fileLayers.forEach(function (layer) {

                                if (entity.layer === layer) {
                                    var feature = objectContext.convertToGeoJSON(entity, element, rgb);

                                    if (feature) {
                                        if (entity.insertHandle) {
                                            layerConfig.geoJSON.inserts[entity.insertHandle] =
                                                layerConfig.geoJSON.inserts[entity.insertHandle] || [];
                                            layerConfig.geoJSON.inserts[entity.insertHandle].push(feature);
                                        }
                                        else
                                            layerConfig.geoJSON.features.push(feature);
                                    }
                                }
                            });
                        });
                    }

                    if(evaluatedLSC.image) {
                        // SVG layer selection and conversion
                        // objectContext.SVGLayerConfig.fileLayers.forEach(function (SVGConfiguration) {
                        evaluatedLSC.image.forEach(function (layerConfig) {
                            layerConfig.fileLayers.forEach(function (layer) {
                                if (entity.layer === layer) {

                                    objectContext.bgGeoJSON.features.push(objectContext.convertToGeoJSON(entity, element, rgb));
                                    objectContext.convertToSVG(acc, entity, element, bbox, rgb);
                                }
                            });
                        });
                    }
                }
            }
            return acc;

        }, {
            bbox: new FMFPconverterViewBox(),
            elements: []
        }),
            bbox = _entitiesReduce.bbox,
            elements = _entitiesReduce.elements;

        var viewBox = bbox.min.x === Infinity ? {
            x: 0,
            y: 0,
            width: 0,
            height: 0
        } : {
                x: bbox.min.x,
                y: -bbox.max.y,
                width: bbox.max.x - bbox.min.x,
                height: bbox.max.y - bbox.min.y
            };
        return {
            SVG: objectContext.utils.format(
                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="150.564294281609631 -78.92068832803213 40.37898729002333 30.72068832803213" width="100%" height="100%">\n' +
                '<g stroke="#000000" stroke-width="0.03" fill="none" transform="matrix(1,0,0,-1,0,0)">\n' +
                '{4}' +
                '</g>\n' +
                '</svg>', viewBox.x, viewBox.y, viewBox.width, viewBox.height, elements.join("\n")),

            geoJSONarr: evaluatedLSC.geojson
        }
    },

    convertToGeoJSON: function (entity, element, rgb) {

        var objectContext = this;
        // TODO: change to make a query to a property that sets if the original point or the converted points are parsed
        if (true) {

            // TODO: Query the layer configuration standard table to find the room layer
            if (entity.layer === 'RM$') {

                return {
                    type: 'Feature',
                    properties: entity.mappingProperties,
                    color: rgb,
                    geometry: {
                        type: entity.type,
                        coordinates: element
                    }
                }
            }
            return {
                type: 'Feature',
                properties: {},
                color: rgb,
                geometry: {
                    type: entity.type,
                    coordinates: element
                }
            }
        }
    },

    convertToSVG: function (acc, entity, elements, bbox, rgb) {
        var path = '';
        elements.forEach(function (item) {
            Object.keys(item).forEach(function (key) {
                if (path != "")
                    path += " ";

                path += key + item[key].join(' ');
            });
        });

        if (entity.transforms.length > 0)
            elementPath =
                '<path \
                rotation="' + entity.transforms[0].rotation + '" \
                x="' + entity.transforms[0].position.x + '" \
                xScale="' + entity.transforms[0].xScale + '" \
                d="' + path + '"\
            ></path>';
        else
            elementPath =
                '<path d="' + path + '"></path>';

        if (entity.layer == 'GROS$') {
            acc.bbox.expandByPoint(bbox.min);
            acc.bbox.expandByPoint(bbox.max);
        }
        acc.elements.push(this.utils.format('<g stroke="{0}">{1}</g>', this.rgbToColorAttribute(rgb), elementPath));
    },


    /**
    * Switch the appropriate function on entity type. CIRCLE, ARC and ELLIPSE
    * produce native SVG elements, the rest produce interpolated polylines.
    */
    entityToBoundsAndElement: function (entity) {
        var objectContext = this;
        switch (entity.type) {
            case 'CIRCLE':
                return objectContext.circle(entity);
            case 'ELLIPSE':
                return objectContext.ellipse(entity);
            case 'ARC':
                return objectContext.arc(entity);
            case 'SPLINE':
                if (entity.degree === 2 || entity.degree === 3) {
                    try {
                        return objectContext.bezier(entity);
                    } catch (err) {
                        return objectContext.polyline(entity);
                    }
                } else
                    return objectContext.polyline(entity);
            case 'LINE':
            case 'LWPOLYLINE':
            case 'POLYLINE':
                return objectContext.polyline(entity);
            default:
                //logger.warn('entity type not supported in SVG rendering:', entity.type);
                return null;
        }
    },

    getRGBForEntity: function (layers, entity) {
        var objectContext = this;
        var layerTable = layers.layers[entity.layer];

        if (layerTable.color)
            return layerTable.color;

        return [0, 0, 0];
    },

    /**
    * Convert an RGB array to a CSS string definition.
    * Converts white lines to black as the default.
    */
    rgbToColorAttribute: function (rgb) {
        if (rgb[0] === 255 && rgb[1] === 255 && rgb[2] === 255) {
            return 'rgb(0, 0, 0)';
        } else {
            return this.utils.format('rgb({0}, {1}, {2})', rgb[0], rgb[1], rgb[2]);
        }
    },

    addFlipXIfApplicable: function (entity, _ref) {
        var bbox = _ref.bbox;
        var element = _ref.element;

        if (entity.extrusionDirection && entity.extrusionDirection.z) {
            return {
                bbox: new FMFPconverterViewBox().expandByPoint({
                    x: -bbox.min.x,
                    y: bbox.min.y
                }).expandByPoint({
                    x: -bbox.max.x,
                    y: bbox.max.y
                }),
                element: '<g transform="matrix(-1 0 0 1 0 0)">\n'
                    .concat(element, '\n</g>')
            };
        } else {
            return {
                bbox: bbox,
                element: element
            };
        }
    },

    /**
    * Rotate a points by the given angle.
    *
    * @param points the points
    * @param angle the rotation angle
    */
    rotate: function (p, angle) {
        return {
            x: p.x * Math.cos(angle) - p.y * Math.sin(angle),
            y: p.y * Math.cos(angle) + p.x * Math.sin(angle)
        };
    },


    denormalize: function (parseResult, configuredLayers, versionSysId, isNewFloor, convertionSource) {

        var blocksByName = parseResult.blocks.reduce(function (acc, b) {
            acc[b.name] = b;
            return acc;
        }, {}),
            objectContext = this,
            layerStandardConfig = configuredLayers.layerStandardConfig,
            entitiesByLayer = configuredLayers.entitiesByLayer;

        parseResult.entities.forEach(function (entity) {
            if (entity.layer === 'RM$') {
                if (/*objectContext.facilityFlag &&*/ entity.extendedData.customStrings[1] && entity.extendedData.customStrings[2]) {
                    objectContext.dxfMapper.generateFacilities(versionSysId, isNewFloor, entity.extendedData.customStrings[1], entity.extendedData.customStrings[2]);
                    return;
                }
            }
        });

        var evaluatedLSC = {};
        
        var gatherEntities = function gatherEntities(entities, transforms) {
            var current = [];

            entities.forEach(function (e) {
                layerStandardConfig.forEach(function (lsc) {
                    if(convertionSource === 'autocad') {
                        var evaluatedTrue = false;
                        lsc.fileLayers.forEach(function (layer) {
                            if (e.layer == layer && objectContext.dxfMapper.evaluateCondition(e, lsc.condition)) {
                                evaluatedTrue = true;

                                var condensedElement = JSON.parse(JSON.stringify(e));
                                var arrRelatedEntities = [];
                                if (e.extendedData && e.extendedData.relatedEntities) {
                                    e.extendedData.relatedEntities.forEach(function (relatedEntity) {
                                        var relatedEntityFound = entities.filter(function (currEntity) {
                                            return currEntity.handle === relatedEntity;
                                        });
                                        arrRelatedEntities.push(relatedEntityFound[0]);
                                    });

                                    if (!gs.nil(condensedElement.extendedData.relatedEntities))
                                        condensedElement.extendedData.relatedEntities = arrRelatedEntities;
                                }
                                var mappingProperties = objectContext.dxfMapper.applyMappings(lsc.sysID, lsc.objectOperation, condensedElement);
                                if (mappingProperties && mappingProperties.coalesceFields && mappingProperties.coalesceFields !== {})
                                    e['mappingProperties'] = mappingProperties;
                            }
                        });
                        if(evaluatedTrue && !lsc.used)
                            lsc.used = true;
                    }
                    else if(!lsc.used)
                        lsc.used = true;
                });

                if (e.type === 'INSERT') {
                    var insert = e;
                    var block = blocksByName[insert.name];
                    if (!block)
                        return;

                    var t = {
                        position: {
                            x: -block.position.x + insert.position.x,
                            y: -block.position.y + insert.position.y,
                        },
                    };

                    if (insert.mappingProperties) t.mappingProperties = insert.mappingProperties;
                    if (insert.xScale) t.xScale = insert.xScale;
                    if (insert.yScale) t.yScale = insert.yScale;
                    if (insert.zScale) t.zScale = insert.zScale;
                    if (insert.rotation) t.rotation = insert.rotation;
                    if (insert.extrusionDirection) {
                        t.extrusionDirection = {};
                        if (insert.extrusionDirection.x) t.extrusionDirection.x = insert.extrusionDirection.x;
                        if (insert.extrusionDirection.y) t.extrusionDirection.y = insert.extrusionDirection.y;
                        if (insert.extrusionDirection.z) t.extrusionDirection.z = insert.extrusionDirection.z;
                    };

                    var transforms2 = transforms.slice(0);
                    transforms2.push(t);

                    // Use the insert layer
                    if (block.entities) {
                        var blockEntities = block.entities.map(function (be) {
                            var be2 = JSON.parse(JSON.stringify(be));
                            be2.layer = insert.layer;
                            if (insert.handle)
                                be2.insertHandle = insert.handle;


                            // Object.keys(entitiesByLayer).forEach(function (layer) {
                            //     if (be2.layer === layer) {
                            if (entitiesByLayer[be2.layer]) {
                                be2.transforms = transforms2;
                                entitiesByLayer[be2.layer].push(JSON.parse(JSON.stringify(be2)));
                            }
                            // });

                            return be2;
                        });

                        current = current.concat(gatherEntities(blockEntities, transforms2));
                    }

                } else {
                    // Top-level entity. Clone and add the transforms
                    // The transforms are reversed so they occur in
                    // order of application - i.e. the transform of the
                    // top-level insert is applied last
                    var e2 = JSON.parse(JSON.stringify(e));
                    e2.transforms = transforms.slice().reverse();

                    if (entitiesByLayer[e2.layer])
                        entitiesByLayer[e2.layer].push(JSON.parse(JSON.stringify(e2)));
                    // Object.keys(entitiesByLayer).forEach(function (layer) {
                    //     if (e2.layer === layer)
                    //         entitiesByLayer[layer].push(JSON.parse(JSON.stringify(e2)));
                    // });

                    current.push(e2);
                }
            });

            return current;
        };

        var runDenormalize = function runDenormalize(entities, transforms) {
            var entitites = gatherEntities(entities, transforms);

            
            layerStandardConfig.forEach(function (lsc) {
                if(lsc.used) {
                    if(!evaluatedLSC[lsc.targetFormat]) {
                        evaluatedLSC[lsc.targetFormat] = [];
                    }

                    evaluatedLSC[lsc.targetFormat].push(lsc);
                }
            });

            return entities;
        };

        return {
            denormalized: runDenormalize(parseResult.entities, []),
            entitiesByLayer: entitiesByLayer,
            evaluatedLSC: evaluatedLSC
        }
    },

    // applyMappings: function (entitiesByLayer, layerStandardConfig, versionSysId, isNewFloor, entities) {

    //     var dxfMapper = new FMFPdxfMapper();
    //     dxfMapper.generateFacilities(
    //         versionSysId, isNewFloor,
    //         entitiesByLayer['RM$'][0].extendedData.customStrings[1],
    //         entitiesByLayer['RM$'][0].extendedData.customStrings[2]
    //     );

    //     layerStandardConfig.forEach(function (lsc) {
    //         lsc.fileLayers.forEach(function (layer) {

    //             if (dxfMapper.evaluateCondition(e, lsc.condition)) {

    //                 var condensedElement = JSON.parse(JSON.stringify(e));
    //                 var arrRelatedEntities = [];
    //                 if (e.extendedData && e.extendedData.relatedEntities) {
    //                     e.extendedData.relatedEntities.forEach(function (relatedEntity) {
    //                         var relatedEntityFound = entities.filter(function (currEntity) {
    //                             return currEntity.handle === relatedEntity;
    //                         });
    //                         arrRelatedEntities.push(relatedEntityFound[0]);
    //                     });

    //                     if (!gs.nil(condensedElement.extendedData.relatedEntities))
    //                         condensedElement.extendedData.relatedEntities = arrRelatedEntities;
    //                 }
    //                 var mappingProperties = dxfMapper.applyMappings(lsc.sysID, lsc.objectOperation, condensedElement);
    //                 if (mappingProperties && mappingProperties.coalesceFields && mappingProperties.coalesceFields !== {})
    //                     e['mappingProperties'] = mappingProperties;
    //             }
    //         })
    //     });
    //     return entitiesByLayer;
    // },

    bezier: function (entity) {
        var objectContext = this;
        var bbox = new FMFPconverterViewBox();
        entity.controlPoints.forEach(function (p) {
            bbox = bbox.expandByPoint(p);
        });
        var k = entity.degreeOfSplineCurve + 1;
        var piecewise = new FMFPtoPiecewiseBezier().toPiecewiseBezier(k, entity.controlPoints, entity.knotValues);
        var element = objectContext.piecewiseToPaths(k, piecewise.controlPoints, entity.transforms);
        //var element = objectContext.utils.format('<g>{0}</g>', paths.join(''));

        return { bbox: bbox, element: element };
    },

    piecewiseToPaths: function (k, controlPoints, transforms) {
        var objectContext = this,
            element = [];
        var nSegments = (controlPoints.length - 1) / (k - 1);
        var paths = [];

        var transformMatrix = objectContext.getTransformMatrix(transforms);

        for (var i = 0; i < nSegments; ++i) {
            var cp = controlPoints.slice(i * (k - 1));

            cp.forEach(function (value, i) {
                transformed = objectContext.tranformXY(value.x, value.y, transformMatrix);
                i === 0 ?
                    element.push({ M: [[transformed.x, transformed.y]] }) :
                    element.push({ C: [[transformed.x, transformed.y]] });

            });
        }
        return paths;
    },

    /**
    * An ARC is an ellipse with equal radii
    */
    arc: function (entity) {
        var objectContext = this;

        // In DXF an extrusionZ value of -1 denote a tranform around the Y axis.
        if (entity.extrusionDirection && entity.extrusionDirection.z) {
            var flipMatrix = objectContext.getFlipMatrix(entity.transforms);
            var fliped = objectContext.tranformXY(entity.center.x, entity.center.y, flipMatrix);
            entity.center.x = fliped.x;
            entity.center.y = fliped.y;
        }

        var _ellipseOrArc = objectContext.ellipseOrArc(
            entity.center.x, entity.center.y,
            entity.radius, entity.radius,
            entity.startAngle, entity.endAngle,
            0, entity.extrusionDirection && entity.extrusionDirection.z === -1,
            entity.transformMatrix,
            entity.transforms
        );

        return _ellipseOrArc;
    },

    polyline: function (entity) {
        var objectContext = this,
            element = [];

        var vertices = objectContext.entityToPolyline.toPolyline(entity);
        var bbox = vertices.reduce(function (acc, _ref) {
            var x = _ref[0],
                y = _ref[1];
            return acc.expandByPoint({
                x: x,
                y: y
            });
        }, new FMFPconverterViewBox());

        //gs.info(JSON.stringify(vertices));
        vertices.forEach(function (point, i) {

            transformed = objectContext.tranformXY(point[0], point[1], entity.transformMatrix);

            i === 0 ?
                element.push({ M: [[transformed.x, transformed.y]] }) :
                element.push({ L: [[transformed.x, transformed.y]] });
        });
        //gs.info(JSON.stringify(element));
        // Empirically it appears that flipping horzontally does not apply to polyline
        //return objectContext.transformBoundingBoxAndElement(bbox, element, entity.transforms);
        return { bbox: bbox, element: element };
    },

    /**
    * An ELLIPSE is defined by the major axis, convert to X and Y radius with
    * a rotation angle
    */
    ellipse: function (entity) {
        var objectContext = this, element = [];
        var rx = Math.sqrt(entity.majorAxisEndPoint.x * entity.majorAxisEndPoint.x + entity.majorAxisEndPoint.y * entity.majorAxisEndPoint.y);
        var ry = entity.axisRatio * rx;
        var majorAxisRotation = -Math.atan2(-entity.majorAxisEndPoint.y, entity.majorAxisEndPoint.x);

        /* Example
        <path d="
            M cx - r, cy
            a rx,ry 0 1,0 (rx * 2),0
            a rx,ry 0 1,0 -(rx * 2),0
        "/>
         */

        // In DXF an extrusionZ value of -1 denote a tranform around the Y axis.
        if (entity.extrusionDirection && entity.extrusionDirection.z) {
            var flipMatrix = objectContext.getFlipMatrix(entity.transforms);
            var fliped = objectContext.tranformXY(entity.center.x, entity.center.y, flipMatrix);
            entity.center.x = fliped.x;
            entity.center.y = fliped.y;
        }

        var _ellipseOrArc = objectContext.ellipseOrArc(
            entity.center.x, entity.center.y,
            rx, ry,
            entity.startAngle, entity.endAngle,
            majorAxisRotation,
            null,
            entity.transformMatrix
        );

        return _ellipseOrArc;
    },

    /**
    * Create a a <path d="A..." /> or <ellipse /> element for the ARC or ELLIPSE
    * DXF entity (<ellipse /> if start and end point are the same).
    */
    ellipseOrArc: function (cx, cy, rx, ry, startAngle, endAngle, rotationAngle, flipX, transformMatrix, transforms) {
        var objectContext = this;
        var bbox = [
            { x: rx, y: ry },
            { x: rx, y: ry },
            { x: -rx, y: -ry },
            { x: -rx, y: ry }
        ].reduce(function (acc, p) {
            var rotated = objectContext.rotate(p, rotationAngle);
            acc.expandByPoint({
                x: cx + rotated.x,
                y: cy + rotated.y
            });
            return acc;
        }, new FMFPconverterViewBox());

        var element = [];
        var elementRotation = rotationAngle / Math.PI * 180;

        if ((Math.abs(startAngle - endAngle) < 1e-9) || (Math.abs(startAngle - endAngle + Math.PI * 2) < 1e-9)) {
            // Use a native <ellipse> when start and end angles are the same, and
            // arc paths with same start and end points don't render (at least on Safari)


            transformed = objectContext.tranformXY(entity.center.x, entity.center.y, transformMatrix);

            /* Example
            <path d="
                M cx - rx, cy
                a rx,ry 0 1,0 (rx * 2),0
                a rx,ry 0 1,0 -(rx * 2),0
            "/>
            */

            element.push({ M: [[transformed.x - rx, transformed.y]] });
            // a -> A
            element.push({
                A: [
                    [rx, ry],
                    [elementRotation],
                    [1, 0],
                    [rx * 2, 0]
                ]
            });
            element.push({
                A: [
                    [rx, ry],
                    [elementRotation],
                    [1, 0],
                    [-(rx * 2), 0]
                ]
            });

            return { bbox: bbox, element: element };

        } else {
            var startOffset = objectContext.rotate({
                x: Math.cos(startAngle) * rx,
                y: Math.sin(startAngle) * ry
            }, rotationAngle);
            var startPoint = {
                x: cx + startOffset.x,
                y: cy + startOffset.y
            };
            var endOffset = objectContext.rotate({
                x: Math.cos(endAngle) * rx,
                y: Math.sin(endAngle) * ry
            }, rotationAngle);
            var endPoint = {
                x: cx + endOffset.x,
                y: cy + endOffset.y
            };
            var adjustedEndAngle = endAngle < startAngle
                ? endAngle + Math.PI * 2
                : endAngle;

            var largeArcFlag = adjustedEndAngle - startAngle < Math.PI ? 0 : 1;
            //var sweepFlag = rotationAngle > 0 ? 0: 1;


            tStartPoints = objectContext.tranformXY(startPoint.x, startPoint.y, transformMatrix);
            tEndPoints = objectContext.tranformXY(endPoint.x, endPoint.y, transformMatrix);

            var sweep = 1;

            if (transforms) {
                if (transforms[0]) {
                    if (transforms[0].xScale) {
                        if (transforms[0].xScale == -1) {
                            sweep = 0;
                        }
                    }
                }
            }

            element.push({ M: [[tStartPoints.x, tStartPoints.y]] });
            element.push({
                A: [
                    [rx, ry],
                    [elementRotation],
                    [largeArcFlag],
                    [sweep],
                    [tEndPoints.x, tEndPoints.y]
                ]
            });

            //var d = objectContext.utils.format('M{0} {1} A{2},{3} {4} {5} 1 {6},{7}', startPoint.x, startPoint.y, rx, ry, rotationAngle / Math.PI * 180, largeArcFlag, endPoint.x, endPoint.y);
            return { bbox: bbox, element: element };
        }
    },

    /**
    * Create a <circle /> element for the CIRCLE entity.
    */
    circle: function (entity) {
        var objectContext = this,
            element = [];

        var bbox = new FMFPconverterViewBox().expandByPoint({
            x: entity.center.x + entity.radius,
            y: entity.center.y + entity.radius
        }).expandByPoint({
            x: entity.center.x - entity.radius,
            y: entity.center.y - entity.radius
        });

        // In DXF an extrusionZ value of -1 denote a tranform around the Y axis.
        if (entity.extrusionDirection && entity.extrusionDirection.z) {
            var flipMatrix = objectContext.getFlipMatrix(entity.transforms);
            var fliped = objectContext.tranformXY(entity.center.x, entity.center.y, flipMatrix);
            entity.center.x = fliped.x;
            entity.center.y = fliped.y;
        }

        transformed = objectContext.tranformXY(entity.center.x, entity.center.y, entity.transformMatrix);

        /* Example
        <path d="
            M cx - r, cy
            a r,r 0 1,0 (r * 2),0
            a r,r 0 1,0 -(r * 2),0
        "/>
         */

        element.push({ M: [[transformed.x - entity.radius, transformed.y]] });
        // a -> A
        element.push({
            a: [
                [entity.radius, entity.radius],
                [0],
                [1, 0],
                [entity.radius * 2, 0]
            ]
        });
        element.push({
            a: [
                [entity.radius, entity.radius],
                [0],
                [1, 0],
                [-(entity.radius * 2), 0]
            ]
        });

        return {
            bbox: bbox,
            element: element
        };
    },
    /*  
    * Transform the bounding box and the SVG element by the given
    * transforms. The <g> element are created in reverse transform
    * order and the bounding box in the given order.
    */
    transformBoundingBoxAndElement: function (bbox, element, transforms) {
        var matrices = transforms.map(function (transform) {

            // Create the transformation matrix
            var tx = transform.position.x || 0;
            var ty = transform.position.y || 0;
            var sx = transform.xScale || 1;
            var sy = transform.yScale || 1;
            var angle = (transform.rotation || 0) / 180 * Math.PI;
            var cos = Math.cos,
                sin = Math.sin;
            var a, b, c, d, e, f;

            // In DXF an extrusionZ value of -1 denote a tranform around the Y axis.
            if (transform.extrusion && transform.extrusion.z === -1) {
                a = -sx * cos(angle);
                b = sx * sin(angle);
                c = sy * sin(angle);
                d = sy * cos(angle);
                e = -tx;
                f = ty;
            } else {
                a = sx * cos(angle);
                b = sx * sin(angle);
                c = -sy * sin(angle);
                d = sy * cos(angle);
                e = tx;
                f = ty;
            }

            return [a, b, c, d, e, f];
        });

        matrices.reverse();
        matrices.forEach(function (_ref2) {
            var a = _ref2[0],
                b = _ref2[1],
                c = _ref2[2],
                d = _ref2[3],
                e = _ref2[4],
                f = _ref2[5];
            // transformedElement += objectContext.utils.format('<g transform="matrix({0} {1} {2} {3} {4} {5})">', a, b, c, d, e, f);

            element.forEach(function (item) {
                Object.keys(item).forEach(function (key) {
                    //arcs don't need to translate
                    if (key !== "a") {

                        for (var i = 0; i < item[key].length; i++) {

                            var x = item[key][i][0],
                                y = item[key][i][1];

                            item[key][i][0] = a * x + c * y + e;
                            item[key][i][1] = b * x + d * y + f;
                        }
                    }
                });
            });

        });
        //gs.info('transform: ' + JSON.stringify(element));
        // transformedElement += element;
        // matrices.forEach(function (transform) {
        //     transformedElement += '</g>';
        // });
        return {
            bbox: bbox,
            element: element
        };
    },
    /*
    * Generates a transform matrix to flip the element
    */
    getFlipMatrix: function (transforms) {
        var matrices = transforms.map(function (transform) {
            // Create the transformation matrix
            var sx = transform.xScale || 1;
            var sy = transform.yScale || 1;
            var angle = (transform.rotation || 0) * (Math.PI / 180); // deg->rad
            var cos = Math.cos;

            return [-sx * cos(angle), 0, 0, sy * cos(angle), 0, 0];
        });

        return matrices;
    },
    getSVGFixMatrix: function () {

        //Não estou a perceber 180 * (Math.PI / 180)

        var a = 180 * (Math.PI / 180); // deg->rad
        var cos = Math.cos,
            sin = Math.sin;

        return [[-cos(a), sin(a), -sin(a), cos(a), 0, 0]];
    },
    getTransformMatrix: function (transforms) {

        var matrices = transforms.map(function (transform) {
            // Create the transformation matrix
            var tx = transform.position.x || 0;
            var ty = transform.position.y || 0;
            var sx = transform.xScale || 1;
            var sy = transform.yScale || 1;
            var angle = (transform.rotation || 0) / 180 * Math.PI;
            var cos = Math.cos,
                sin = Math.sin;
            var a, b, c, d, e, f;

            // In DXF an extrusionZ value of -1 denote a tranform around the Y axis.
            if (transform.extrusion && transform.extrusion.z === -1) {
                a = -sx * cos(angle);
                b = sx * sin(angle);
                c = sy * sin(angle);
                d = sy * cos(angle);
                e = -tx;
                f = ty;
            } else {
                a = sx * cos(angle);
                b = sx * sin(angle);
                c = -sy * sin(angle);
                d = sy * cos(angle);
                e = tx;
                f = ty;
            }

            return [a, b, c, d, e, f];
        });

        return matrices;
    },
    /*
    *Calculates the transform value for x and y based on a transform matix
    */
    tranformXY: function (x, y, transformMatrix) {
        transformMatrix.reverse();
        transformMatrix.forEach(function (ref) {
            var a = ref[0],
                b = ref[1],
                c = ref[2],
                d = ref[3],
                e = ref[4],
                f = ref[5];

            var pX = x, pY = y;

            x = a * pX + c * pY + e;
            y = b * pX + d * pY + f;
        });

        return { 'x': x, 'y': y };
    },
    toWCS: function (pointXYZ, extrusion_vector) {

        var ax = {};
        var ay = {};
        if (abs(extrusion_vector[x]) < 1 / 64 && abs(extrusion_vector[y]) < 1 / 64) {
            ax.x = extrusion_vector[z];
            ax.y = 0;
            ax.z = - extrusion_vector[x];
        }
        else {
            ax.x = - extrusion_vector[y];
            ax.y = extrusion_vector[x];
            ax.z = 0;
        }

        // eleva cada elemento de ax ao quadrado, soma tudo e eleva a 0.5
        //mx = sum([i ** 2 for i in ax])** .5   #calculate magnitude of ax 
        //ax = ax * 1 / mx   #scale ax to have magnitude 1

        ay.x = extrusion_vector[y] * ax[z] - extrusion_vector[z] * ax[y];
        ay.y = extrusion_vector[z] * ax[x] - extrusion_vector[x] * ax[z];
        ay.z = extrusion_vector[x] * ax[y] - extrusion_vector[y] * ax[x];
        az = extrusion_vector;
    }

};